/* ************************************************************************

   Copyright:

   License:

   Authors:

************************************************************************ */

/* ************************************************************************



************************************************************************ */

/**
* The MapView class provides coordinate management and layer management. Also provides
* support including drag panning, rubber-band box drawing, measure and area tools.
*
* @event mouseup {qx.event.type.MouseEvent} (Fired by {@link qx.event.handler.EventHandler})
* @event mousedown {qx.event.type.MouseEvent} (Fired by {@link qx.event.handler.EventHandler})
* @event mousemove {qx.event.type.MouseEvent} (Fired by {@link qx.event.handler.EventHandler})
* @param vMapObj {mmclient.mapscript.MapObj} MapObj must have at least width, height and extent
* values for both MapObj and ReferenceMapObj.
*/
qx.Class.define("mmclient.MapView",
{
  extend : qx.ui.container.Composite,


  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function(vMapObj)
  {
    this.base(arguments);

    // Configure layout
    var layout = new qx.ui.layout.Basic;
    this.setLayout(layout);
    this.setBackgroundColor("#FFFFFF");
    this.setMapObj(vMapObj);
    
    this._createMapPane();
  },


  /*
  *****************************************************************************
     STATICS
  *****************************************************************************
  */

  statics :
  {
    // Unit Types, values are stored as Meters:
    UNIT_DD          : 8.99938115,
    UNIT_CENTIMETERS : 100.00000000,
    UNIT_INCHES      : 39.37007870,
    UNIT_FEET        : 3.28083990,
    UNIT_METERS      : 1.00000000,
    UNIT_KILOMETERS  : 0.00100000,
    UNIT_MILES       : 0.00062137,

    // Available Tools:
    TOOL_PAN         : 0,
    TOOL_ZOOMIN      : 1,
    TOOL_ZOOMOUT     : 2
  },


  /*
  *****************************************************************************
     PROPERTIES
  *****************************************************************************
  */

  properties :
  {
    /**
    * A mapscript MapObj to be viewed and manipulated.
    *
    */
    mapObj :
    {
      check : "Object",
      init : null
    },

    x1 :
    {
      check : "Number",
      init : -1
    },

    y1 :
    {
      check : "Number",
      init : -1
    },

    x2 :
    {
      check : "Number",
      init : -1
    },

    y2 :
    {
      check : "Number",
      init : -1
    },

    offsetX :
    {
      check : "Number",
      init : 0
    },

    offsetY :
    {
      check : "Number",
      init : 0
    },

    minScale :
    {
      check : "Number",
      init : -1
    },

    maxScale :
    {
      check : "Number",
      init : -1
    },

    dragging :
    {
      check : "Boolean",
      init : false
    },
    
    jitter :
    {
      check : "Number",
      init : 10
    },

    /**
    * The zoom factor to be applied on zoom-in operations (the inverse
    * is taken for zoom-out operations).
    */
    zoomSize :
    {
      check : "Number",
      init : 2
    },

    /**
    * Direction of zoom.
    * <p>
    * <ul>
    *  <li>-1 = out</li>
    *  <li>0 = none (pan)</li>
    *  <li>1 = in</li>
    * </ul>
    * </p>
    *
    * @type zoomDir {Integer} Pan to start (0).
    */
    zoomDir :
    {
      check : "Number",
      init : 0
    },

    /**
    * Use getScalebar().getDisplay() to see if the scalebar is displayed or not.
    *
    */
    scalebar :
    {
      check : "Object",
      init : null
    },

    /**
    * The units of the scale bar image.
    *
    */
    screenUnit :
    {
      check : "Number",
      init : 39.37010000
    },

    /**
    * The units the scale bar is representing.
    *
    */
    scaleUnit :
    {
      check : "Number",
      init : 1.00000000
    },

    /**
    * Not sure what this is...???
    *
    */
    mapUnit :
    {
      check : "Number",
      init : -1
    },

    /**
    * Default extent.
    *"mmclient.mapscript.RectObj"
    */
    defaultExtent :
    {
      check : "Object",
      init : null
    },

    /**
    * Reference View
    *"mmclient.ReferenceView"
    */
    referenceView :
    {
      check : "Object",
      init : null
    },
    
    /**
    * Whether or not the control has been initialized
    */
    initialized :
    {
      check : "Boolean",
      init : false
    },

    /**
    * Holds the currently selected tool.
    * Use the changeTool method to select a different tool using the
    * pre-defined TOOL_* constants.
    *
    */
    _tool :
    {
      check : "Number",
      init : 0
    }
  },
  
  events :
  {
  /**
   * Fired after the Default Extent has been aquired.
   */
  gotDefaultExtent : 'qx.event.type.Data'
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    /**
     * TODOC
     *
     * @type member
     * @return {void} 
     */
    _createMapPane : function()
    {
      this._mapField = new qx.ui.basic.Image();
      this._mapField.setAllowShrinkX(true);
      this._mapField.setAllowShrinkY(true);

      this._mapField.addListener("mousedown", this._mapFieldMouseDown, this);
      this._mapField.addListener("mouseup", this._mapFieldMouseUp, this);
      this._mapField.addListener("mousemove", this._mapFieldMouseMove, this);
      this._mapField.addListener("mouseout", function(e) {
        this.setDragging(false);
      }, this);
      
      this.add(this._mapField);
      
      this._drawField = new qx.ui.core.Widget();
      this._drawField.setCursor("crosshair");   
      this._drawField.setVisibility("hidden");
      
      this.add(this._drawField);

      this._drawField.addListener("mousemove", function(e) {
        // Auto Resize------------------------------
        if (this.getInitialized())
        {
          var loc = this.getContentLocation("box");
          if (loc != null)
          {
            var w = loc["right"] - loc["left"];
            var h = loc["bottom"] - loc["top"];
            if (this.getMapObj().getWidth() != w || 
                this.getMapObj().getHeight() != h)
            {
              this.setDimension(
                w, 
                h,
                this.getMapObj().getExtent()
              );
            }
          }
        }
        //-----------------------------------------
      }, this);      

      this._cr = new mmclient.cropper.CropperMod(this._drawField, 'crop').set({
        cropActive        : true,
        cropType          : "full",
        cropMarginLeft    : 0,
        cropMarginRight   : 0,
        cropMarginTop     : 0,
        cropMarginBottom  : 0,
        cropPaddingLeft   : 0,
        cropPaddingRight  : 0,
        cropPaddingTop    : 0,
        cropPaddingBottom : 0
      });
      
      this._cr.addListener('cropSelected', function(e) {
        var d = e.getData();
        //alert(d.left + " " + d.top + " " + d.width + " " + d.height);
        
        this.setDragging(false);
        if (this.getInitialized() && this.get_tool() == mmclient.MapView.TOOL_ZOOMIN)
        {
          // variables for jitter test
          var x1 = Math.min(d.left, d.left + d.width);
          var x2 = Math.max(d.left, d.left + d.width);
          var y1 = Math.min(d.top, d.top + d.height);
          var y2 = Math.max(d.top, d.top + d.height);
          
          if (x1 != x2 && y1 != y2 && x2 - x1 > this.getJitter() && y2 - y1 > this.getJitter()) {
            this.applyBox(x1, y1, x2, y2);
          }
          else
          {
            this.applyZoom(x1, y1);
          }
          
          this.changeImage(this.getMapObj().getWidth(), this.getMapObj().getHeight(), this.getMapObj().getExtent());
          this.getReferenceView().drawRefBox(this.getMapObj().getExtent());
        }
      },this);
    },

    /* ************************
        MAP PANNING / ZOOM-OUT
       ************************ */

    /**
     * TODOC
     *
     * @type member
     * @param e {Event} TODOC
     * @return {void} 
     */
    _mapFieldMouseDown : function(e)
    {
      this.setDragging(true);
      if (this.getInitialized())
      {
        this.setX1(e.getDocumentLeft() - this.getOffsetX());
        this.setX2(e.getDocumentLeft() - this.getOffsetX());
        this.setY1(e.getDocumentTop() - this.getOffsetY());
        this.setY2(e.getDocumentTop() - this.getOffsetY());
      }
    },

    /**
     * TODOC
     *
     * @type member
     * @param e {Event} TODOC
     * @return {void} 
     */
    _mapFieldMouseMove : function(e)
    {

      
      if (this.getInitialized())
      {
        // Auto Resize------------------------------
        var loc = this.getContentLocation("box");
        if (loc != null)
        {
          var w = loc["right"] - loc["left"];
          var h = loc["bottom"] - loc["top"];
          if (this.getMapObj().getWidth() != w || 
              this.getMapObj().getHeight() != h)
          {
            this.setDimension(
              w, 
              h,
              this.getMapObj().getExtent()
            );
          }
        }
        //-----------------------------------------

        if (this.get_tool() == mmclient.MapView.TOOL_PAN && this.getDragging() == true)
        {
          this.setX2(e.getDocumentLeft() - this.getOffsetX());
          this.setY2(e.getDocumentTop() - this.getOffsetY());
  
          var x = this.getX2() - this.getX1();
          var y = this.getY2() - this.getY1();
          this._mapField.setUserBounds(x, y, this.getMapObj().getWidth(), this.getMapObj().getHeight());
        }
      }
    },

    /**
     * TODOC
     *
     * @type member
     * @param e {Event} TODOC
     * @return {void} 
     */
    _mapFieldMouseUp : function(e)
    {
      this.setDragging(false);
      if (this.getInitialized())
      {
        if (this.get_tool() == mmclient.MapView.TOOL_PAN)
        {
          this.setX2(e.getDocumentLeft() - this.getOffsetX());
          this.setY2(e.getDocumentTop() - this.getOffsetY());
  
          var x, y;
          if (this.getX1() != this.getX2() || this.getY1() != this.getY2())
          {  
            var w = this.getMapObj().getWidth();
            var h = this.getMapObj().getHeight();
            var x3 = this.getX2() - this.getX1();
            var y3 = this.getY2() - this.getY1();
            x = (w / 2) - x3;
            y = (h / 2) - y3;
  
            this.applyZoom(x, y);
          }
          else
          {
            x = this.getX1();
            y = this.getY1();
            
            this.applyZoom(x, y);
          }
                  
          this.changeImage(this.getMapObj().getWidth(), this.getMapObj().getHeight(), this.getMapObj().getExtent());
          this.getReferenceView().drawRefBox(this.getMapObj().getExtent());        
        }
        else if (this.get_tool() == mmclient.MapView.TOOL_ZOOMOUT)
        {
          this.applyZoom(this.getX1(), this.getY1());
          
          this.changeImage(this.getMapObj().getWidth(), this.getMapObj().getHeight(), this.getMapObj().getExtent());
          this.getReferenceView().drawRefBox(this.getMapObj().getExtent());
        }
      }
    },

    /* ****************************
        END MAP PANNING / ZOOM-OUT
       **************************** */

    changeTool : function(tool)
    {
      this.set_tool(tool);

      if (tool == mmclient.MapView.TOOL_ZOOMIN)
      {
        // set zoom-in flags
        this.setZoomDir(1);
        this._drawField.setVisibility('visible');
      }
      else if (tool == mmclient.MapView.TOOL_ZOOMOUT)
      {
        // set zoom-out flags
        this.setZoomDir(-1);
        this._drawField.setVisibility('hidden');
      }
      else if (tool == mmclient.MapView.TOOL_PAN)
      {
        // set pan flags
        this.setZoomDir(0);
        this._drawField.setVisibility('hidden');
      }
    },
    /**
     * TODOC
     *
     * @type member
     * @param w {var} TODOC
     * @param h {Map} TODOC
     * @return {void} 
     */
     
    setDimension : function(w, h, ext)
    {
//this.debug(w + " x " + h);

      this.getMapObj().getCellSize(0);      
      this.getMapObj().setWidth(w);
      this.getMapObj().setHeight(h);
      this.getMapObj().setExtent(new mmclient.mapscript.RectObj(ext.getMinX(), ext.getMinY(), ext.getMaxX(), ext.getMaxY()));

      this.setOffsetX(this.getBounds().left);
      this.setOffsetY(this.getBounds().top);
      
      this._drawField.setWidth(w);
      this._drawField.setHeight(h);

      this.applyBox(0, 0, w, h);

      this.changeImage(
        this.getMapObj().getWidth(),
        this.getMapObj().getHeight(),
        this.getMapObj().getExtent()
      );
      
      this.getReferenceView().initialize();
      this.getReferenceView().drawRefBox(this.getMapObj().getExtent());
    },

    /**
     * TODOC
     *
     * @type member
     * @return {void} 
     */
    changeImage : function(w, h, ext)
    {
      var self = this;

      var rpc = new qx.io.remote.Rpc("http://localhost/services/", "qooxdoo.mmserv");
      rpc.setCrossDomain(true);
      rpc.setTimeout(10000);

      var setMapImg = function(result, exc, id)
      {
        if (exc == null)
        {
          //alert("Result of async(" + id + ") call: " + result);
          self._mapField.setSource("http://localhost" + result);
        }
        else
        {
          alert("Exception during async(" + id + ") call: " + exc);
        }
        self._mapField.setUserBounds(0, 0, w, h); 
      };

      rpc.callAsync(setMapImg, "getMapImg",
      {
        doc_id : qx.core.Init.getApplication().getAppHistory().getState(),
        width  : w,
        height : h,
        minx   : ext.getMinX(),
        miny   : ext.getMinY(),
        maxx   : ext.getMaxX(),
        maxy   : ext.getMaxY()
      });
    },

    /* *****************
        FROM mapserv.js
       ***************** */

    /**
     * Calculate a new map extent based on zoomdir, zoomsize, and
     * the passed point (in image coordinates), and store in the
     * {@link mapmedley.mapscript.RectObj} (extent) field.
     *
     * @type member
     * @param x {Integer} Image coordinate X.
     * @param y {Integer} Image coordinate Y.
     * @return {void} 
     */
    applyZoom : function(x, y)
    {
      var dx, dy, mx, my;
      var zoom;

      if (this.getMapObj().getCellSize() == 0) {
        this.getMapObj().setCellSize(
          this._adjustExtent(
            this.getMapObj().getExtent(), 
            this.getMapObj().getWidth(), 
            this.getMapObj().getHeight()
          )
        );
      }

      if (this.getZoomDir() == 1 && this.getZoomSize() != 0)
      {
        zoom = this.getZoomSize();
      }
      else if (this.getZoomDir() == -1 && this.getZoomSize() != 0)
      {
        zoom = 1 / this.getZoomSize();
      }
      else
      {
        zoom = 1;
      }

      dx = this.getMapObj().getExtent().getMaxX() - this.getMapObj().getExtent().getMinX();
      dy = this.getMapObj().getExtent().getMaxY() - this.getMapObj().getExtent().getMinY();
      // convert *click* to map coordinates
      mx = this.getMapObj().getExtent().getMinX() + this.getMapObj().getCellSize() * x;
      my = this.getMapObj().getExtent().getMaxY() - this.getMapObj().getCellSize() * y;

      this.getMapObj().getExtent().setMinX(mx - 0.5 * (dx / zoom));
      this.getMapObj().getExtent().setMinY(my - 0.5 * (dy / zoom));
      this.getMapObj().getExtent().setMaxX(mx + 0.5 * (dx / zoom));
      this.getMapObj().getExtent().setMaxY(my + 0.5 * (dy / zoom));

      this.getMapObj().setCellSize(
        this._adjustExtent(
          this.getMapObj().getExtent(), 
          this.getMapObj().getWidth(), 
          this.getMapObj().getHeight()
        )
      );

      if (this.getMinScale() != -1 && this.getScale() < this.getMinScale())
      {
        x = (this.getMapObj().getExtent().getMaxX() + this.getMapObj().getExtent().getMinX()) / 2;
        y = (this.getMapObj().getExtent().getMaxY() + this.getMapObj().getExtent().getMinY()) / 2;
        this.setExtentFromScale(x, y, this.getMinScale());
      }
      
      if (this.maxscale != -1 && this.getScale() > this.maxscale)
      {
        x = (this.getMapObj().getExtent().getMaxX() + this.getMapObj().getExtent().getMinX()) / 2;
        y = (this.getMapObj().getExtent().getMaxY() + this.getMapObj().getExtent().getMinY()) / 2;
        this.setExtentFromScale(x, y, this.getMaxScale());
      }
    },

    /**
     * Convert a box defined in image coordinates to an extent defined in map
     * coordinates and store in the {@link mapmedley.mapscript.RectObj} (extent) field.
     *
     * @type member
     * @param x1 {Integer} Minimum X image coordinate.
     * @param y1 {Integer} Minimum Y image coordinate.
     * @param x2 {Integer} Maximum X image coordinate.
     * @param y2 {Integer} Maximum Y image coordinate.
     * @return {void} 
     */
    applyBox : function(x1, y1, x2, y2)
    {
      var x;
      var y;
      var minx;
      var miny;
      var maxx;
      var maxy;

      if (this.getMapObj().getCellSize() == 0) {
        this.getMapObj().setCellSize(
          this._adjustExtent(
            this.getMapObj().getExtent(), 
            this.getMapObj().getWidth(), 
            this.getMapObj().getHeight()
          )
        );
      }

      minx = this.getMapObj().getExtent().getMinX() + this.getMapObj().getCellSize() * x1;
      miny = this.getMapObj().getExtent().getMaxY() - this.getMapObj().getCellSize() * y2;
      maxx = this.getMapObj().getExtent().getMinX() + this.getMapObj().getCellSize() * x2;
      maxy = this.getMapObj().getExtent().getMaxY() - this.getMapObj().getCellSize() * y1;

      var tmpExt = new mmclient.mapscript.RectObj(minx, miny, maxx, maxy);
      this.getMapObj().setExtent(tmpExt);
      this.getMapObj().setCellSize(
        this._adjustExtent(
          this.getMapObj().getExtent(), 
          this.getMapObj().getWidth(), 
          this.getMapObj().getHeight()
        )
      );

      if (this.getMinScale() != -1 && this.getScale() < this.getMinScale())
      {
        x = (this.getMapObj().getExtent().getMaxX() + this.getMapObj().getExtent().getMinX()) / 2;
        y = (this.getMapObj().getExtent().getMaxY() + this.getMapObj().getExtent().getMinY()) / 2;
        this.setExtentFromScale(x, y, this.getMinScale());
      }

      if (this.getMaxScale() != -1 && this.getScale() > this.getMaxScale())
      {
        x = (this.getMapObj().getExtent().getMaxX() + this.getMapObj().getExtent().getMinX()) / 2;
        y = (this.getMapObj().getExtent().getMaxY() + this.getMapObj().getExtent().getMinY()) / 2;
        this.setExtentFromScale(x, y, this.getMaxScale());
      }
    },
    
    /**
     * Get the nominal scale based on current settings.
     * Returns scale {Double} The nominal scale.
     *
     * @type member
     * @return {var} TODOC
     */
    getScale : function()
    {
      var gd, md;

      md = (this.getMapObj().getWidth() - 1) / (this.getMapObj().getResolution() * (mmclient.MapView.UNIT_INCHES / this.getMapUnit()));
      gd = this.getMapObj().getExtent().getMaxX() - this.getMapObj().getExtent().getMinX();

      return (gd / md);
    },    
    
    /**
     * Set {@link mapmedly.mapscript.RectObj} (extent) centered on the given point using the given scale.
     *
     * @type member
     * @param x {Double} Map coordinate X value.
     * @param y {Double} Map coordinate Y value.
     * @param scale {Integer} Desired scale, given as denominator of the representative fraction (1:x).
     * @return {void} 
     */
    setExtentFromScale : function(x, y, scale)
    {
      // remove leading 1: and any commas
      // var scale = 1.0*scale.replace(/,|1:/g,"");
      if ((this.getMinScale() != -1) && (scale < this.getMinScale())) scale = this.getMinScale();

      if ((this.getMaxScale() != -1) && (scale > this.getMaxscale())) scale = this.getMaxScale();

      this.getMapObj().setCellSize(
        (scale / this.getMapObj().getResolution()) / this.getMapUnit()
      );

      this.getMapObj().getExtent().setMinX(
        x - this.getMapObj().getCellSize() * this.getMapObj().getWidth() / 2.0      
      );
      this.getMapObj().getExtent().setMinY(
        y - this.getMapObj().getCellSize() * this.getMapObj().getHeight() / 2.0
      );
      this.getMapObj().getExtent().setMaxX(
        x + this.getMapObj().getCellSize() * this.getMapObj().getWidth() / 2.0
      );
      this.getMapObj().getExtent().setMaxY(
        y + this.getMapObj().getCellSize() * this.getMapObj().getHeight() / 2.0
      );

      this.getMapObj().setCellSize(
        this._adjustExtent(
          this.getMapObj().getExtent(), 
          this.getMapObj().getWidth(), 
          this.getMapObj().getHeight()
        )
      );
    },    
    
    /**
     * Adjust values of given extent to fit width and height.
     * Returns {Double} Ground resolution of pixels in map image.
     *
     * @type member
     * @param extent {mapmedely.mapscript.RectObj} Extent to be adjusted.
     * @param width {Integer} Width of map image in pixels.
     * @param height {Integer} Height of map image in pixels.
     * @return {var} TODOC
     */
    _adjustExtent : function(extent, width, height)
    {
      var cellsize = Math.max((extent.getMaxX() - extent.getMinX()) / width, (extent.getMaxY() - extent.getMinY()) / height);

      if (cellsize > 0)
      {
        var ox = Math.max((width - (extent.getMaxX() - extent.getMinX()) / cellsize) / 2, 0);
        var oy = Math.max((height - (extent.getMaxY() - extent.getMinY()) / cellsize) / 2, 0);

        extent.setMinX(extent.getMinX() - ox * cellsize);
        extent.setMinY(extent.getMinY() - oy * cellsize);
        extent.setMaxX(extent.getMaxX() + ox * cellsize);
        extent.setMaxY(extent.getMaxY() + oy * cellsize);
      }

      return (cellsize);
    },    
    
    /**
     * TODOC
     *
     * @type member
     * @param mmdoc {State} TODOC
     * @return {void} 
     */
    initialize : function()
    {
      this.debug("MapView Initializing...");
      
      // Set some initial values
      this.setDefaultExtent(new mmclient.mapscript.RectObj(-180, -90, 180, 90));
      this.setMapUnit(mmclient.MapView.UNIT_DD);
      this.setScreenUnit(mmclient.MapView.UNIT_INCHES);
      this.setScaleUnit(mmclient.MapView.UNIT_METERS);
      this.changeTool(mmclient.MapView.TOOL_PAN);

      // Need to wait until MapView appears to get Location.
      this.addListenerOnce("appear", function() {        
        var rpc = new qx.io.remote.Rpc("http://localhost/services/", "qooxdoo.mmserv");
        rpc.setCrossDomain(true);
        rpc.setTimeout(10000);
 
        var self = this; 
        var setLayerExtent = function(result, exc, id)
        {
          if (exc == null)
          {
            //alert("Result of async(" + id + ") call: " + result);
            //self.debug("MinX: " + result["minx"] + " MinY: " + result["miny"] + " MaxX: " + result["maxx"] + " MaxY: " + result["maxy"]);
            self.setDefaultExtent(new mmclient.mapscript.RectObj(
              Math.round(result["minx"]),
              Math.round(result["miny"]),
              Math.round(result["maxx"]),
              Math.round(result["maxy"])
              )
            );
            self.fireDataEvent('gotDefaultExtent', null);
          }
          else
          {
            alert("Exception during async(" + id + ") call: " + exc);
          } 
        };
  
        rpc.callAsync(setLayerExtent, "getLayerExtent",
        {
          layername   : "countries_simpl"
        });
      }, this);      
      
      this.addListener("gotDefaultExtent", function() {        
        var loc = this.getContentLocation("box");
        var w = loc["right"] - loc["left"];
        var h = loc["bottom"] - loc["top"];
        this.setDimension(w, h, this.getDefaultExtent());
        this.setInitialized(true);
      }, this);
    }
  }
});
